#include <iostream>
#include <sstream>
#include "chat_server.h"
#include "../common/util.hpp"

namespace server{
//---------------------------启动服务器--------------------------------------
int ChatServer::Start(const std::string& ip,short port)
{
  //创建socket
  sock=socket(AF_INET,SOCK_DGRAM,0);
  if(sock<0)
  {
    perror("socket");
    return -1;
  }
  //绑定端口号
  sockaddr_in addr;
  addr.sin_family=AF_INET;
  addr.sin_addr.s_addr=inet_addr(ip.c_str());
  addr.sin_port=htons((port));
  int ret=bind(sock,(sockaddr*)&addr,sizeof(addr));
  if(ret<0)
  {
    perror("bind");
    return -1;
  }
  std::cout<<"server start ok!"<<std::endl;
  
  //创建两个线程（生产者/消费者）
  pthread_t pro,con;
  pthread_create(&pro,NULL,Product,this);
  pthread_create(&con,NULL,Consume,this);
  pthread_join(pro,NULL);
  pthread_join(con,NULL);
  return 0;
}
//--------------------生产者线程-------------------------------------------
void* ChatServer::Product(void* arg)
{
  //生产者线程的主要工作就是循环从socket中读取数据再写到消息队列中去
  ChatServer* server=reinterpret_cast<ChatServer*>(arg);
  while(true)
  {
    //RecvMsg负责读取一次数据并写入消息队列中
    server->RecvMsg();
  }
  return NULL;
}

//--------------------消费者线程--------------------------------------
void* ChatServer::Consume(void* arg)
{
  //消费者线程负责从消息队列中循环读取数据并进行广播
  ChatServer* server=reinterpret_cast<ChatServer*>(arg);
  while(true)
  {
    server->BoardCast();
  }
  return NULL;
}
//---------------------接收消息------------------------------------
int ChatServer::RecvMsg()
{
   //1.从sock中读数据
   char buf[1024*5]={0};
   sockaddr_in peer;
   socklen_t len=sizeof(peer);
   ssize_t read_size=recvfrom(sock,buf,sizeof(buf),0,(sockaddr*)&peer,&len);
   if(read_size<0)
   {
     perror("recvfrom");
     return -1;
   }
   buf[read_size]='\0';
   printf("recvmsg : %s\n",buf);
  //2.将数据加入消息队列
  Context context;
  context.str=buf;
  context.addr=peer;
  msg_queue.PushQueue(context);
  return 0;
}
//---------------------广播消息------------------------------------
int ChatServer::BoardCast()
{
  //1.从队列中读取一条消息
  Context context;
  msg_queue.PopQueue(&context);
  //2.对取出的消息进行反序列化
  Data data;
  data.Unserialize(context.str);
  //3.根据消息内容更新在线好友列表
  //  a)若该好友不在消息列表中，将其加入进来
  //  b)若改好友已下线，则从好友列表中进行删除
  if(data.cmd=="quit")
  {
    DelUser(context.addr,data.name);
  }
  else
  {
    AddUser(context.addr,data.name);
  }
  //4.遍历在线好友列表，将这个消息依次发给每个用户
  //因为发送消息的用户自己本身也在好友列表中，所以在遍历列表时也会自己给自己发消息
  //从而达到发送者也能看到自己发送的消息的效果。但这种方式要经过网络传输，影响性能
  //可自己实现在客户端进行控制使得自己发的消息自己也能够看到
  for(auto item:online_friend_list)
    //这里的content_str相当于是一个Data结构体序列化后的字符串
    SendMsg(context.str,item.second);
  return 0;
}
//---------------------新用户加入到好友列表-------------------------------------
void ChatServer::AddUser(sockaddr_in addr,const std::string& name)
{
  //map中的key采用ip和name进行拼接的方式
  //仅仅使用ip的话并不能唯一标识一个用户，因为若有多个用户都在
  //同一个局域网中，服务器看到的ip地址都是相同的（NAT机制）
  //ip+端口号可以区分同一个局域网中的不同主机
  //
  //这里的stringstream可使内容输出到它的对象ss中
  std::stringstream ss;
  ss<<name<<":"<<addr.sin_addr.s_addr;

  //1.若该用户不存在就插入
  //2.若存在，就修改
  //valueType& operator[](const keyType& key)
  //const valueType& operator[](const keyType& key)const
  //
  //map中的insert方法其返回值类型是sdt::pair
  //其中第一个是迭代器（指向插入成功后的位置），第二个是bool（表示插入成功)
  online_friend_list[ss.str()]=addr;
  return;
}

//----------------------从列表中删除下线好友-------------------------------------
void ChatServer::DelUser(sockaddr_in addr,const std::string& name)
{
  std::stringstream ss;
  ss<<name<<":"<<addr.sin_addr.s_addr;
  online_friend_list.erase(ss.str());
  return;
}
//----------------------向每个用户发送消息---------------------------------------
void ChatServer::SendMsg(const std::string& data,sockaddr_in addr)
{
  //将该消息发给客户端
  sendto(sock,data.data(),data.size(),0,(sockaddr*)&addr,sizeof(addr));
}
}
